package com.oddfar.campus.framework.api.file;

import com.oddfar.campus.common.constant.Constants;
import com.oddfar.campus.common.exception.file.FileNameLengthLimitExceededException;
import com.oddfar.campus.common.exception.file.FileSizeLimitExceededException;
import com.oddfar.campus.common.exception.file.InvalidExtensionException;
import com.oddfar.campus.common.utils.DateUtils;
import com.oddfar.campus.common.utils.StringUtils;
import com.oddfar.campus.common.utils.uuid.Seq;
import com.oddfar.campus.framework.api.sysconfig.ConfigExpander;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.UUID;

/**
 * 文件上传工具类
 *
 * @author xianziye
 */
public class FileUploadUtils {
    private static final Logger log = LoggerFactory.getLogger(FileUploadUtils.class);

    /**
     * 默认大小 50M
     */
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;

    /**
     * 默认的文件名最大长度 100
     */
    public static final int DEFAULT_FILE_NAME_LENGTH = 100;

    /**
     * 默认上传的地址
     */
    private static String defaultBaseDir = ConfigExpander.getFileProfile();

    public static void setDefaultBaseDir(String defaultBaseDir) {
        FileUploadUtils.defaultBaseDir = defaultBaseDir;
    }

    public static String getDefaultBaseDir() {
        return defaultBaseDir;
    }

    /**
     * 以默认配置进行文件上传
     *
     * @param file 上传的文件
     * @return 文件名称
     * @throws Exception
     */
    public static final String upload(MultipartFile file) throws IOException {
        try {
            return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
        } catch (Exception e) {
            throw new IOException(e.getMessage(), e);
        }
    }

    /**
     * 根据文件路径上传
     *
     * @param baseDir 相对应用的基目录
     * @param file    上传的文件
     * @return 文件名称
     * @throws IOException
     */
    public static final String upload(String baseDir, MultipartFile file) throws IOException {
        try {
            return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
        } catch (Exception e) {
            throw new IOException(e.getMessage(), e);
        }
    }

    /**
     * 文件上传
     *
     * @param baseDir          相对应用的基目录
     * @param file             上传的文件
     * @param allowedExtension 上传文件类型
     * @return 返回上传成功的文件名
     * @throws FileSizeLimitExceededException       超出最大大小
     * @throws FileNameLengthLimitExceededException 文件名太长
     * @throws IOException                          读写文件出错
     * @throws InvalidExtensionException            文件校验异常
     */
    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
            InvalidExtensionException {

        // 检查文件名长度是否超过限制（requireNonNull判断参数是否是一个空对象，是则抛出空指针异常）
        int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
            // 抛出文件名字过长业务异常
            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
        }

        assertAllowed(file, allowedExtension); // 检查文件扩展名和文件大小是否被允许

//        String fileName = extractFilename(file); // 从文件对象中提取文件名
//
//        // 计算文件的绝对路径，并将文件传输到该路径
//        String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
//        file.transferTo(Paths.get(absPath));// 将内存中的文件内容转移到由 absPath 指定的实际磁盘位置

        String extension = getExtension(file); // 获取文件名后缀

//        return getPathFileName(baseDir, fileName); // 返回构建好的资源路径
        return UUID.randomUUID() + "." + extension; // 返回随机生成的文件名
    }

    /**
     * 从MultipartFile对象中提取并构建文件名
     * 新文件名格式为：日期路径/原始文件名_序列编号.扩展名
     *
     * @param file MultipartFile对象，包含上传的文件信息
     * @return 构建后的文件名字符串
     */
    public static final String extractFilename(MultipartFile file) {

        return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
                FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
    }

    /**
     * 根据指定的基础上传目录和文件名，生成并确保目标文件所在的绝对路径存在。
     * 如果目标文件或其父目录不存在，则创建所需的目录结构。
     *
     * @param uploadDir 基础上传目录的路径字符串。
     * @param fileName 需要生成绝对路径的目标文件名。
     * @return 绝对路径指向的File对象。
     * @throws IOException 如果在创建所需目录结构时发生I/O错误。
     */
    public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException {
        // 创建一个File对象，表示目标文件的绝对路径
        File desc = new File(uploadDir + File.separator + fileName);

        // 检查目标文件是否存在，如果不存在
        if (!desc.exists()) {
            // 获取目标文件的父目录并检查它是否存在
            if (!desc.getParentFile().exists()) {
                // 如果父目录不存在，则创建它以及所有必要的祖先目录
                desc.getParentFile().mkdirs();
            }
        }
        return desc;
    }

    /**
     * 根据上传目录和文件名构建资源路径。
     * 这个方法首先获取配置文件中定义的系统基础文件路径，
     * 然后截取传入的上传目录相对于基础路径的部分，最后组合成资源前缀与上传目录、文件名共同构成的完整资源路径。
     *
     * @param uploadDir 上传目录的路径，相对于系统基础文件路径。
     * @param fileName 需要获取完整路径的文件名。
     * @return 返回格式化后的资源路径字符串，如："resourcePrefix/path/to/uploadDir/filename.ext"
     * @throws IOException 如果在读取系统基础文件路径配置时出现I/O异常。
     */
    public static final String getPathFileName(String uploadDir, String fileName) throws IOException {
        // 获取配置文件中定义的系统基础文件路径，并计算其长度加一，用于截取相对路径
        int dirLastIndex = ConfigExpander.getFileProfile().length() + 1;

        // 截取上传目录相对于基础路径的部分
        String currentDir = StringUtils.substring(uploadDir, dirLastIndex);

        // 组合资源前缀、上传目录和文件名
        return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
    }

    /**
     * 检查上传的文件是否满足允许的大小和扩展名要求
     *
     * @param file 待检查的MultipartFile对象，即用户上传的文件
     * @param allowedExtension 允许的文件扩展名数组，仅允许上传的文件类型必须与此数组中列出的扩展名相匹配
     * @throws FileSizeLimitExceededException 文件大小超过默认的最大限制时抛出异常
     * @throws InvalidExtensionException 文件扩展名不在允许的扩展名列表中，则抛出异常
     */
    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, InvalidExtensionException {

        // 检查文件大小是否超过默认最大值
        long size = file.getSize();
        if (size > DEFAULT_MAX_SIZE) {
            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
        }

        String fileName = file.getOriginalFilename(); // 获取原始文件名
        String extension = getExtension(file); // 获取文件扩展名

        // 检查文件扩展名是否在允许的扩展名列表中
        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {
            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) {
                // 不允许的图像扩展名
                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
                        fileName);
            } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) {
                // 不允许的Flash扩展名
                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
                        fileName);
            } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) {
                // 不允许的媒体扩展名
                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
                        fileName);
            } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) {
                // 不允许的视频扩展名
                throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
                        fileName);
            } else {
                throw new InvalidExtensionException(allowedExtension, extension, fileName);
            }
        }
    }

    /**
     * 判断MIME类型是否是允许的MIME类型
     *
     * @param extension 要判断是否合法的文件类型
     * @param allowedExtension 合法的文件类型数组
     */
    public static final boolean isAllowedExtension(String extension, String[] allowedExtension) {
        for (String str : allowedExtension) {
            if (str.equalsIgnoreCase(extension)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取文件名的后缀
     *
     * @param file 表单文件
     * @return 后缀名
     */
    public static final String getExtension(MultipartFile file) {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        if (StringUtils.isEmpty(extension)) {
            extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
        }
        return extension;
    }
}
